C.S.Burner 🪙 ━►🔥GIT Node
Node / reticulum_mirror / MeshChatX / files / meshchatx / src / frontend / components / filesync / FilesyncFileManager.vue
Displaying Raw • Download
meshchatx/src/frontend/components/filesync/FilesyncFileManager.vue master (f0f6ed57) Text, 11.18 KB
T8b949e<!-- SPDX-License-Identifier: 0BSD -->
Tff7b72<templateTff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffspace-y-4Ta5d6ff"Tff7b72>
Tff7b72<p Te6edf3class=Ta5d6ff"Ta5d6fftext-sm text-sem-fg-mutedTa5d6ff"Tff7b72>{{ Te6edf3$t("rns_filesync.manager_help") }}Tff7b72</p>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffflex flex-wrap items-center gap-2Ta5d6ff"Tff7b72>
Tff7b72<button
Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffsecondary-chip px-3 py-1.5 text-smTa5d6ff"
Te6edf3:disabled=Ta5d6ff"Ta5d6ffbusy || currentPath === Ta5d6ff'Ta5d6ff'Ta5d6ff"
Te6edf3:title=Ta5d6ff"Te6edf3$tTa5d6ff(Ta5d6ff'Ta5d6ffrns_filesync.browser_upTa5d6ff'Ta5d6ff)Ta5d6ff"
@Te6edf3click=Ta5d6ff"Ta5d6ffgoUpTa5d6ff"
Tff7b72>
Tff7b72<MaterialDesignIcon Te6edf3icon-name=Ta5d6ff"Ta5d6ffarrow-upTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffw-4 h-4Ta5d6ff" Tff7b72/>
{{ Te6edf3$t("rns_filesync.browser_up") }}
Tff7b72</button>
Tff7b72<button Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffsecondary-chip px-3 py-1.5 text-smTa5d6ff" Te6edf3:disabled=Ta5d6ff"Ta5d6ffbusyTa5d6ff" @Te6edf3click=Ta5d6ff"Ta5d6ffrefreshTa5d6ff"Tff7b72>
{{ Te6edf3$t("rns_filesync.refresh") }}
Tff7b72</button>
Tff7b72<button Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffsecondary-chip px-3 py-1.5 text-smTa5d6ff" Te6edf3:disabled=Ta5d6ff"Ta5d6ffbusyTa5d6ff" @Te6edf3click=Ta5d6ff"Ta5d6fftriggerUploadTa5d6ff"Tff7b72>
Tff7b72<MaterialDesignIcon Te6edf3icon-name=Ta5d6ff"Ta5d6ffuploadTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffw-4 h-4Ta5d6ff" Tff7b72/>
{{ Te6edf3$t("rns_filesync.upload") }}
Tff7b72</button>
Tff7b72<button
Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffsecondary-chip px-3 py-1.5 text-smTa5d6ff"
Te6edf3:disabled=Ta5d6ff"Ta5d6ffbusy || !syncDirectoryTa5d6ff"
@Te6edf3click=Ta5d6ff"Te6edf3$emitTa5d6ff(Ta5d6ff'Ta5d6ffopen-folderTa5d6ff'Ta5d6ff)Ta5d6ff"
Tff7b72>
Tff7b72<MaterialDesignIcon Te6edf3icon-name=Ta5d6ff"Ta5d6fffolder-open-outlineTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffw-4 h-4Ta5d6ff" Tff7b72/>
{{ Te6edf3$t("rns_filesync.open_folder") }}
Tff7b72</button>
Tff7b72<input Te6edf3ref=Ta5d6ff"Ta5d6fffileInputTa5d6ff" Te6edf3type=Ta5d6ff"Ta5d6fffileTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffhiddenTa5d6ff" @Te6edf3change=Ta5d6ff"Ta5d6ffonUploadSelectedTa5d6ff" Tff7b72/>
Tff7b72</div>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffinput-field !py-2 font-mono text-xs truncateTa5d6ff" Te6edf3:title=Ta5d6ff"Ta5d6ffbreadcrumbLabelTa5d6ff"Tff7b72>
{{ breadcrumbLabel }}
Tff7b72</div>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffflex flex-col sm:flex-row gap-2Ta5d6ff"Tff7b72>
Tff7b72<input
Te6edf3v-model=Ta5d6ff"Ta5d6ffnewFolderNameTa5d6ff"
Te6edf3type=Ta5d6ff"Ta5d6fftextTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffinput-field flex-1 min-w-0 text-smTa5d6ff"
Te6edf3:placeholder=Ta5d6ff"Te6edf3$tTa5d6ff(Ta5d6ff'Ta5d6ffrns_filesync.browser_new_placeholderTa5d6ff'Ta5d6ff)Ta5d6ff"
Te6edf3:disabled=Ta5d6ff"Ta5d6ffbusyTa5d6ff"
@keydown.enter.Te6edf3prevent=Ta5d6ff"Ta5d6ffcreateFolderTa5d6ff"
Tff7b72/>
Tff7b72<button
Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffsecondary-chip px-3 py-2 text-sm shrink-0Ta5d6ff"
Te6edf3:disabled=Ta5d6ff"Ta5d6ffbusy || !newFolderName.trim()Ta5d6ff"
@Te6edf3click=Ta5d6ff"Ta5d6ffcreateFolderTa5d6ff"
Tff7b72>
Tff7b72<MaterialDesignIcon Te6edf3icon-name=Ta5d6ff"Ta5d6fffolder-plus-outlineTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffw-4 h-4Ta5d6ff" Tff7b72/>
{{ Te6edf3$t("rns_filesync.browser_new") }}
Tff7b72</button>
Tff7b72</div>
Tff7b72<div Te6edf3v-if=Ta5d6ff"Ta5d6ffbusy && entries.length === 0Ta5d6ff" Te6edf3class=Ta5d6ff"Ta5d6fftext-sm text-sem-fg-mutedTa5d6ff"Tff7b72>
{{ Te6edf3$t("rns_filesync.manager_loading") }}
Tff7b72</div>
Tff7b72<div Te6edf3v-else-if=Ta5d6ff"Ta5d6ffentries.length === 0Ta5d6ff" Te6edf3class=Ta5d6ff"Ta5d6fftext-sm text-sem-fg-mutedTa5d6ff"Tff7b72>
{{ Te6edf3$t("rns_filesync.manager_empty") }}
Tff7b72</div>
Tff7b72<ul v-else Te6edf3class=Ta5d6ff"Ta5d6ffspace-y-2Ta5d6ff"Tff7b72>
Tff7b72<li
Te6edf3v-for=Ta5d6ff"Ta5d6ffentry in entriesTa5d6ff"
Te6edf3:key=Ta5d6ff"Ta5d6ffentry.pathTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffflex flex-col sm:flex-row sm:items-center justify-between gap-2 p-3 rounded-lg border border-sem-borderTa5d6ff"
Tff7b72>
Tff7b72<button
Te6edf3v-if=Ta5d6ff"Ta5d6ffentry.type === Ta5d6ff'Ta5d6ffdirTa5d6ff'Ta5d6ff"
Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffmin-w-0 flex items-center gap-2 text-left text-sm text-sem-fg hover:text-emerald-600 dark:hover:text-emerald-400Ta5d6ff"
@Te6edf3click=Ta5d6ff"Ta5d6ffenterDir(entry.path)Ta5d6ff"
Tff7b72>
Tff7b72<MaterialDesignIcon
Te6edf3icon-name=Ta5d6ff"Ta5d6fffolderTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffw-5 h-5 shrink-0 text-emerald-600 dark:text-emerald-400Ta5d6ff"
Tff7b72/>
Tff7b72<span Te6edf3class=Ta5d6ff"Ta5d6ffbreak-allTa5d6ff"Tff7b72>{{ entry.name }}Tff7b72</span>
Tff7b72</button>
Tff7b72<div v-else Te6edf3class=Ta5d6ff"Ta5d6ffmin-w-0 flex items-center gap-2 text-sm text-sem-fgTa5d6ff"Tff7b72>
Tff7b72<MaterialDesignIcon Te6edf3icon-name=Ta5d6ff"Ta5d6fffile-outlineTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffw-5 h-5 shrink-0 text-sem-fg-mutedTa5d6ff" Tff7b72/>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffmin-w-0Ta5d6ff"Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffbreak-allTa5d6ff"Tff7b72>{{ entry.name }}Tff7b72</div>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6fftext-xs text-sem-fg-muted mt-0.5Ta5d6ff"Tff7b72>
{{ formatFileSize(entry.size) }}
Tff7b72</div>
Tff7b72</div>
Tff7b72</div>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffflex flex-wrap gap-2 shrink-0Ta5d6ff"Tff7b72>
Tff7b72<button
Te6edf3v-if=Ta5d6ff"Ta5d6ffentry.type === Ta5d6ff'Ta5d6fffileTa5d6ff'Ta5d6ff"
Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffsecondary-chip px-3 py-1.5 text-smTa5d6ff"
Te6edf3:disabled=Ta5d6ff"Ta5d6ffbusyTa5d6ff"
@Te6edf3click=Ta5d6ff"Ta5d6ffdownloadEntry(entry)Ta5d6ff"
Tff7b72>
{{ Te6edf3$t("rns_filesync.download_local") }}
Tff7b72</button>
Tff7b72<button
Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffsecondary-chip px-3 py-1.5 text-sm text-red-600 dark:text-red-300Ta5d6ff"
Te6edf3:disabled=Ta5d6ff"Ta5d6ffbusyTa5d6ff"
@Te6edf3click=Ta5d6ff"Ta5d6ffdeleteEntry(entry)Ta5d6ff"
Tff7b72>
{{ Te6edf3$t("rns_filesync.delete") }}
Tff7b72</button>
Tff7b72</div>
Tff7b72</li>
Tff7b72</ul>
Tff7b72</div>
Tff7b72</template>
Tff7b72<scriptTff7b72>
import MaterialDesignIcon from "../MaterialDesignIcon.vue";
import ToastUtils from "../../js/ToastUtils";
import DownloadUtils from "../../js/DownloadUtils";
import DialogUtils from "../../js/DialogUtils";
import Utils from "../../js/Utils";
export default {
name: "FilesyncFileManager",
components: {
MaterialDesignIcon,
},
props: {
syncDirectory: {
type: String,
default: "",
},
},
emits: ["open-folder"],
data() {
return {
busy: false,
currentPath: "",
parentPath: null,
entries: [],
newFolderName: "",
};
},
computed: {
breadcrumbLabel() {
if (!this.currentPath) {
return this.$t("rns_filesync.manager_root");
}
return this.currentPath;
},
},
watch: {
syncDirectory() {
this.currentPath = "";
this.refresh();
},
},
mounted() {
this.refresh();
},
methods: {
formatFileSize(bytes) {
return Utils.formatBytes(bytes || 0);
},
joinPath(base, name) {
const left = String(base || "").replace(/\/+$/, "");
const right = String(name || "").replace(/^\/+/, "");
if (!left) {
return right;
}
if (!right) {
return left;
}
return `${left}/${right}`;
},
async refresh() {
this.busy = true;
try {
const params = {};
if (this.currentPath) {
params.path = this.currentPath;
}
const response = await window.api.get("/api/v1/filesync/tree", { params });
const data = response?.data || {};
this.entries = Array.isArray(data.entries) ? data.entries : [];
this.currentPath = data.current != null ? String(data.current) : "";
this.parentPath = data.parent === undefined ? null : data.parent;
} catch (err) {
this.entries = [];
ToastUtils.error(err?.response?.data?.message || err?.message || this.$t("rns_filesync.error"));
} finally {
this.busy = false;
}
},
enterDir(path) {
this.currentPath = String(path || "");
this.refresh();
},
goUp() {
if (this.parentPath === null || this.parentPath === undefined) {
return;
}
this.currentPath = this.parentPath === "" ? "" : String(this.parentPath);
this.refresh();
},
triggerUpload() {
this.$refs.fileInput?.click();
},
async onUploadSelected(event) {
const file = event?.target?.files?.[0];
if (!file) {
return;
}
this.busy = true;
try {
const formData = new FormData();
formData.append("file", file);
if (this.currentPath) {
formData.append("path", this.currentPath);
}
await window.api.post("/api/v1/filesync/upload", formData);
ToastUtils.success(this.$t("rns_filesync.upload_done"));
await this.refresh();
} catch (err) {
ToastUtils.error(err?.response?.data?.message || err?.message || this.$t("rns_filesync.error"));
} finally {
this.busy = false;
if (event?.target) {
event.target.value = "";
}
}
},
async createFolder() {
const name = String(this.newFolderName || "").trim();
if (!name) {
return;
}
this.busy = true;
try {
const path = this.joinPath(this.currentPath, name);
await window.api.post("/api/v1/filesync/mkdir", { path });
ToastUtils.success(this.$t("rns_filesync.browser_created"));
this.newFolderName = "";
await this.refresh();
} catch (err) {
ToastUtils.error(err?.response?.data?.message || err?.message || this.$t("rns_filesync.error"));
} finally {
this.busy = false;
}
},
async downloadEntry(entry) {
const path = entry?.path;
if (!path) {
return;
}
this.busy = true;
try {
const response = await window.api.get("/api/v1/filesync/content", {
params: { path },
responseType: "blob",
});
await DownloadUtils.downloadFromApiResponse(response, entry.name || "download");
ToastUtils.success(this.$t("rns_filesync.download_local_done"));
} catch (err) {
ToastUtils.error(err?.response?.data?.message || err?.message || this.$t("rns_filesync.error"));
} finally {
this.busy = false;
}
},
async deleteEntry(entry) {
const path = entry?.path;
if (!path) {
return;
}
const label = entry.name || path;
if (!(await DialogUtils.confirm(this.$t("rns_filesync.delete_confirm", { name: label })))) {
return;
}
this.busy = true;
try {
await window.api.delete("/api/v1/filesync/entry", { data: { path } });
ToastUtils.success(this.$t("rns_filesync.delete_done"));
await this.refresh();
} catch (err) {
ToastUtils.error(err?.response?.data?.message || err?.message || this.$t("rns_filesync.error"));
} finally {
this.busy = false;
}
},
},
};
Tff7b72</script>
Served by rngit 1.4.2 - Generated in 0.03s